/**
 * Anderground project - Subway maps processor application.
 * Copyright (C) 2008-2009 Eldar Abusalimov, Ilya Shurigyn
 * 
 * This file is part of Anderground.
 * 
 * Anderground is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * Anderground is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Anderground.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anderground.core.util;

import java.io.Serializable;

/**
 * Time class.
 * 
 * working with Time typed values
 * 
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 * @author Ilya Shourigyn (honorik@gmail.com)
 */
public class Time implements Serializable {

	private static final long serialVersionUID = 2104071009945307600L;

	private int seconds, minutes, hours;

	transient private String string;

	/**
	 * @param hours
	 *            - Hours
	 * @param minutes
	 *            - Minutes
	 * @param seconds
	 *            - Seconds
	 * @throws TimeFormatException
	 *             - in case of invalid arguments
	 */
	public Time(int hours, int minutes, int seconds) throws TimeFormatException {
		this.hours = hours;
		this.minutes = minutes;
		this.seconds = seconds;
		checkValuesOrThrow();
	}

	/**
	 * @param seconds
	 *            - Time in seconds
	 * @throws TimeFormatException
	 *             - in case of invalid arguments
	 */
	public Time(int seconds) throws TimeFormatException {
		if (seconds < 0) {
			throw new TimeFormatException("seconds cannot be less than 0");
		}
		this.seconds = seconds % 60;
		this.minutes = (seconds / 60) % 60;
		this.hours = seconds / 3600;
	}

	/**
	 * 
	 * @param s
	 *            time in format "hh:mm:ss" or "mm:ss" or "ss"
	 * @throws TimeFormatException
	 *             - in case of wrong time format
	 */
	public Time(String s) throws TimeFormatException {
		this(s, ':');
	}

	/**
	 * 
	 * @param s
	 *            time in format "hh[separator]mm[separator]ss" or
	 *            "mm[separator]ss" or "ss"
	 * @param separator
	 * @throws TimeFormatException
	 *             - in case of wrong time format
	 */
	public Time(String s, char separator) throws TimeFormatException {

		try {
			int firstColon;
			int secondColon;
			firstColon = s.indexOf(separator);
			if (firstColon == -1) {
				seconds = Integer.parseInt(s);
				checkValuesOrThrow();
				return;
			}
			secondColon = s.indexOf(separator, firstColon + 1);
			if (secondColon == -1) {
				minutes = Integer.parseInt(s.substring(0, firstColon));
				seconds = Integer.parseInt(s.substring(firstColon + 1)) % 60;
				checkValuesOrThrow();
				return;
			}
			if ((firstColon > 0) & (secondColon > 0)
					& (secondColon < s.length() - 1)) {
				hours = Integer.parseInt(s.substring(0, firstColon));
				minutes = Integer.parseInt(s.substring(firstColon + 1,
						secondColon));
				seconds = Integer.parseInt(s.substring(secondColon + 1));
				checkValuesOrThrow();
				return;
			} else {
				throw new TimeFormatException("unable to parse time string");
			}
		} catch (NumberFormatException e) {
			throw new TimeFormatException(e);
		}
	}

	/**
	 * 
	 */
	public Time() {
	}

	/**
	 * Checks the values ranges and throws a {@link TimeFormatException} if they
	 * are invalid.
	 * 
	 * @throws TimeFormatException
	 */
	private void checkValuesOrThrow() throws TimeFormatException {
		if (!checkValues()) {
			throw new TimeFormatException(
					"invalid values for hours, minutes or seconds");
		}
	}

	/**
	 * Checks the values ranges.
	 * 
	 * @return true if the ranges of the values are valid, false otherwise.
	 */
	private boolean checkValues() {
		if (hours < 0) {
			return false;
		}
		;
		if (minutes < 0 || minutes > 59) {
			return false;
		}
		if (seconds < 0 || seconds > 59) {
			return false;
		}
		return true;
	}

	/**
	 * @return the seconds
	 */
	public int getSeconds() {
		return seconds;
	}

	/**
	 * @return the minutes
	 */
	public int getMinutes() {
		return minutes;
	}

	/**
	 * @return the hours
	 */
	public int getHours() {
		return hours;
	}

	/**
	 * @return hours * 3600 + minutes * 60 + seconds
	 */
	public int toSeconds() {
		return hours * 3600 + minutes * 60 + seconds;
	}

	/**
	 * @return string in format "hh:mm:ss" if Hours is more then zero, or
	 *         "mm:ss" otherwise
	 */
	@Override
	public String toString() {
		if (string == null) {
			String minuteString;
			String secondString;

			if (minutes < 10) {
				minuteString = "0" + minutes;
			} else {
				minuteString = Integer.toString(minutes);
			}
			if (seconds < 10) {
				secondString = "0" + seconds;
			} else {
				secondString = Integer.toString(seconds);
			}

			if (hours == 0) {
				string = minuteString + ":" + secondString;
			} else if (hours < 10) {
				string = "0" + hours + ":" + minuteString + ":" + secondString;
			} else {
				string = hours + ":" + minuteString + ":" + secondString;
			}
		}
		return string;
	}

	@Override
	public int hashCode() {
		return toString().hashCode();
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		} else if (o instanceof Time) {
			Time another = (Time) o;
			return (this.hours == another.hours
					&& this.minutes == another.minutes && this.seconds == another.seconds);
		} else {
			return false;
		}
	}

	/**
	 * 
	 * @param strTime
	 *            time in format "hh:mm:ss" or "mm:ss" or "ss"
	 * @return time in seconds
	 * @throws TimeFormatException
	 *             - in case of wrong time format
	 */
	public static int stringToSeconds(String strTime)
			throws TimeFormatException {
		return new Time(strTime).toSeconds();
	}

	/**
	 * @param timeInSeconds
	 * @return formated time in string
	 */
	public static String secondsToString(int timeInSeconds) {
		return new Time(timeInSeconds).toString();
	}

	/**
	 * @param s
	 *            time in format "hh:mm:ss" or "mm:ss" or "ss"
	 * @return the {@link Time} value represented in the parameter
	 * @throws TimeFormatException
	 *             - in case of wrong time format
	 */
	public static Time valueOf(String s) throws TimeFormatException {
		return new Time(s);
	}

}
